package com.njtoyo.taxi.admin.logic.finance;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.njtoyo.taxi.admin.library.common.Enum;
import com.njtoyo.taxi.admin.mapper.master.entity.Company;
import com.njtoyo.taxi.admin.mapper.master.entity.GeoCanton;
import com.njtoyo.taxi.admin.mapper.master.mapper.*;
import com.njtoyo.taxi.admin.mongo.repository.primary.CouponMongoRepository;
import com.njtoyo.taxi.admin.rest.presenter.finance.*;
import com.njtoyo.taxi.admin.rest.presenter.finance.vo.FinanceAllowanceCouponMongoStatsVo;
import com.njtoyo.taxi.admin.rest.presenter.finance.vo.FinanceAllowanceStatsAmountVo;
import com.njtoyo.taxi.admin.rest.presenter.finance.vo.FinanceAllowanceStatsCountVo;
import com.njtoyo.taxi.admin.rest.presenter.finance.vo.FinanceDriverRewardAmountVo;
import com.njtoyo.taxi.admin.rest.wrapper.finance.dto.FinanceIncomeBelongStatDto;
import com.njtoyo.taxi.admin.rest.wrapper.finance.dto.FinanceReconciliationDto;
import com.njtoyo.taxi.admin.rest.wrapper.finance.dto.FinanceStatDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

@Service
public class FinanceLogic {
    @Autowired
    private GeoCantonMapper geoCantonMapper;

    @Autowired
    private CouponMapper couponMapper;

    @Autowired
    private RechargeMapper rechargeMapper;

    @Autowired
    private CustomerIncomeMapper customerIncomeMapper;

    @Autowired
    private DriverRewardMapper driverRewardMapper;

    @Autowired
    private RideOrderMapper rideOrderMapper;

    @Autowired
    private CouponMongoRepository couponMongoRepository;

    @Autowired
    private IncomeBelongingLogMapper incomeBelongingLogMapper;

    @Autowired
    private WechatRedpacketWithdrawMapper wechatRedpacketWithdrawMapper;

    @Autowired
    private RideTransactionLogMapper rideTransactionLogMapper;

    @Autowired
    private ActivityWaveAllowanceLogMapper activityWaveAllowanceLogMapper;

    @Autowired
    private ScanRideOrderMapper scanRideOrderMapper;

    @Autowired
    private DriverWithdrawMapper driverWithdrawMapper;

    @Autowired
    private DriverMoneyDeductingMapper driverMoneyDeductingMapper;


    /**
     * 计算统计补贴相关的信息（包括各项补贴的总额，以及补贴单数，平均每单补贴金额）
     * （按全平台与各地区分别统计，并拼装结果返回）
     */
    public List<FinanceAllowanceStatsPresenter> allowanceStat(FinanceStatDto dto) {
        List<FinanceAllowanceStatsPresenter> res = new ArrayList<>();

        // coupon抵扣金额
        List<FinanceAllowanceStatsAmountVo> couponDeductedAmountStat = selectCouponDeductedAmount(dto.getStartTimeStamp(), dto.getEndTimeStamp());
        // 乘客APP打车后奖励
        List<FinanceAllowanceStatsAmountVo> customerAppRideRewardStat = selectCustomerAppRideReward(dto.getStartDate(), dto.getEndDate());
        // 乘客小程序打车后红包
        List<FinanceAllowanceStatsAmountVo> customerWxminaRideRewardStat = selectCustomerWxminaRideReward(dto.getStartDate(), dto.getEndDate());
        // 司机完成行程后奖励
        List<FinanceAllowanceStatsAmountVo> DriverRideRewardStat = selectDriverRideReward(dto.getStartDate(), dto.getEndDate());
        // 线上支付总单数
        List<FinanceAllowanceStatsCountVo> onlineRideCountStat = selectOnlineRideCount(dto.getStartDate(), dto.getEndDate());

        // 平台总计
        FinanceAllowanceStatsPresenter platform = new FinanceAllowanceStatsPresenter();
        FinanceAllowanceStatsPresenter.MainBody platformBody = new FinanceAllowanceStatsPresenter.MainBody();
        FinanceAllowanceStatsPresenter.MainData platformData = new FinanceAllowanceStatsPresenter.MainData();

        BigDecimal platformCouponDeductedAmount = couponDeductedAmountStat.stream().map(FinanceAllowanceStatsAmountVo::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        platformData.setCouponDeductedAmount(NumberUtil.round(platformCouponDeductedAmount, 2));

        BigDecimal platformCustomerAppRideReward = customerAppRideRewardStat.stream().map(FinanceAllowanceStatsAmountVo::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        platformData.setCustomerAppRideReward(NumberUtil.round(platformCustomerAppRideReward, 2));

        BigDecimal platformCustomerWxminaRideReward = customerWxminaRideRewardStat.stream().map(FinanceAllowanceStatsAmountVo::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        platformData.setCustomerWxminaRideReward(NumberUtil.round(platformCustomerWxminaRideReward, 2));

        BigDecimal platformDriverRideReward = DriverRideRewardStat.stream().map(FinanceAllowanceStatsAmountVo::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        platformData.setDriverRideReward(NumberUtil.round(platformDriverRideReward, 2));

        Long platformOnlineRideCount = onlineRideCountStat.stream().mapToLong(FinanceAllowanceStatsCountVo::getNum).sum();
        platformData.setOnlineRideCount(platformOnlineRideCount);

        if (!platformOnlineRideCount.equals(0L)) {
            BigDecimal totalAmount = NumberUtil.add(platformCouponDeductedAmount, platformCustomerAppRideReward, platformCustomerWxminaRideReward, platformDriverRideReward);
            platformData.setAllowancePerRide(NumberUtil.round(NumberUtil.div(totalAmount, platformOnlineRideCount), 2));
        }

        platformBody.setMainBodyType(Enum.MainBodyType.platform);

        platform.setMainData(platformData);
        platform.setMainBody(platformBody);

        // 设置平台总计
        res.add(platform);

        Map<Long, BigDecimal> couponDeductedAmountStatMap = null;
        Map<Long, BigDecimal> customerAppRideRewardStatMap = null;
        Map<Long, BigDecimal> customerWxminaRideRewardStatMap = null;
        Map<Long, BigDecimal> DriverRideRewardStatMap = null;
        Map<Long, Long> onlineRideCountStatMap = null;

        if (ObjectUtil.isNotEmpty(couponDeductedAmountStat)) {
            couponDeductedAmountStatMap = couponDeductedAmountStat.stream().collect(Collectors.toMap(FinanceAllowanceStatsAmountVo::getCantonId, FinanceAllowanceStatsAmountVo::getAmount));
        }
        if (ObjectUtil.isNotEmpty(customerAppRideRewardStat)) {
            customerAppRideRewardStatMap = customerAppRideRewardStat.stream().collect(Collectors.toMap(FinanceAllowanceStatsAmountVo::getCantonId, FinanceAllowanceStatsAmountVo::getAmount));
        }
        if (ObjectUtil.isNotEmpty(customerWxminaRideRewardStat)) {
            customerWxminaRideRewardStatMap = customerWxminaRideRewardStat.stream().collect(Collectors.toMap(FinanceAllowanceStatsAmountVo::getCantonId, FinanceAllowanceStatsAmountVo::getAmount));
        }
        if (ObjectUtil.isNotEmpty(DriverRideRewardStat)) {
            DriverRideRewardStatMap = DriverRideRewardStat.stream().collect(Collectors.toMap(FinanceAllowanceStatsAmountVo::getCantonId, FinanceAllowanceStatsAmountVo::getAmount));
        }
        if (ObjectUtil.isNotEmpty(onlineRideCountStat)) {
            onlineRideCountStatMap = onlineRideCountStat.stream().collect(Collectors.toMap(FinanceAllowanceStatsCountVo::getCantonId, FinanceAllowanceStatsCountVo::getNum));
        }

        Map<Long, BigDecimal> finalCouponDeductedAmountStatMap = couponDeductedAmountStatMap;
        Map<Long, BigDecimal> finalCustomerAppRideRewardStatMap = customerAppRideRewardStatMap;
        Map<Long, BigDecimal> finalCustomerWxminaRideRewardStatMap = customerWxminaRideRewardStatMap;
        Map<Long, BigDecimal> finalDriverRideRewardStatMap = DriverRideRewardStatMap;
        Map<Long, Long> finalOnlineRideCountStatMap = onlineRideCountStatMap;
        dto.getCantonList().forEach(canton -> {
            FinanceAllowanceStatsPresenter cantonItemRes = new FinanceAllowanceStatsPresenter();
            FinanceAllowanceStatsPresenter.MainBody cantonResBody = new FinanceAllowanceStatsPresenter.MainBody();
            FinanceAllowanceStatsPresenter.MainData cantonResData = new FinanceAllowanceStatsPresenter.MainData();

            // mainBody
            cantonResBody.setMainBodyType(Enum.MainBodyType.canton);
            cantonResBody.setId(canton.getId());
            cantonResBody.setName(canton.getName());

            cantonItemRes.setMainBody(cantonResBody);

            // mainData
            BigDecimal itemCouponDeductedAmountStat = BigDecimal.ZERO;
            if (Objects.nonNull(finalCouponDeductedAmountStatMap)) {
                itemCouponDeductedAmountStat = finalCouponDeductedAmountStatMap.getOrDefault(canton.getId(), BigDecimal.ZERO);
                cantonResData.setCouponDeductedAmount(itemCouponDeductedAmountStat);
            }

            BigDecimal itemCustomerAppRideRewardStat = BigDecimal.ZERO;
            if (Objects.nonNull(finalCustomerAppRideRewardStatMap)) {
                itemCustomerAppRideRewardStat = finalCustomerAppRideRewardStatMap.getOrDefault(canton.getId(), BigDecimal.ZERO);
                cantonResData.setCustomerAppRideReward(itemCustomerAppRideRewardStat);
            }

            BigDecimal itemCustomerWxminaRideRewardStat = BigDecimal.ZERO;
            if (Objects.nonNull(finalCustomerWxminaRideRewardStatMap)) {
                itemCustomerWxminaRideRewardStat = finalCustomerWxminaRideRewardStatMap.getOrDefault(canton.getId(), BigDecimal.ZERO);
                cantonResData.setCustomerWxminaRideReward(itemCustomerWxminaRideRewardStat);
            }

            BigDecimal itemDriverRideRewardStat = BigDecimal.ZERO;
            if (Objects.nonNull(finalDriverRideRewardStatMap)) {
                itemDriverRideRewardStat = finalDriverRideRewardStatMap.getOrDefault(canton.getId(), BigDecimal.ZERO);
                cantonResData.setDriverRideReward(itemDriverRideRewardStat);
            }

            Long itemOnlineRideCountStat = 0L;
            if (Objects.nonNull(finalOnlineRideCountStatMap)) {
                itemOnlineRideCountStat = finalOnlineRideCountStatMap.getOrDefault(canton.getId(), 0L);
                cantonResData.setOnlineRideCount(itemOnlineRideCountStat);

                if (!itemOnlineRideCountStat.equals(0L)) {
                    BigDecimal itemTotalAmount = NumberUtil.add(itemCouponDeductedAmountStat, itemCustomerAppRideRewardStat, itemCustomerWxminaRideRewardStat, itemDriverRideRewardStat);
                    cantonResData.setAllowancePerRide(NumberUtil.round(NumberUtil.div(itemTotalAmount, itemOnlineRideCountStat), 2));
                }
            }

            cantonItemRes.setMainData(cantonResData);

            res.add(cantonItemRes);
        });

        return res;
    }

    private List<FinanceAllowanceStatsAmountVo> selectCouponDeductedAmount(Integer startTimeStamp, Integer endTimeStamp) {
        return couponMapper.selectCouponDeductedAmount(startTimeStamp, endTimeStamp);
    }

    private List<FinanceAllowanceStatsAmountVo> selectCustomerAppRideReward(Date startDate, Date endDate) {
        return rechargeMapper.selectCustomerAppRideReward(startDate, endDate);
    }

    private List<FinanceAllowanceStatsAmountVo> selectCustomerWxminaRideReward(Date startDate, Date endDate) {
        return customerIncomeMapper.selectCustomerWxminaRideReward(startDate, endDate);
    }

    private List<FinanceAllowanceStatsAmountVo> selectDriverRideReward(Date startDate, Date endDate) {
        return driverRewardMapper.selectDriverRideReward(startDate, endDate);
    }

    private List<FinanceAllowanceStatsCountVo> selectOnlineRideCount(Date startDate, Date endDate) {
        return rideOrderMapper.selectOnlineRideCount(startDate, endDate);
    }

    public List<GeoCanton> selectCantonExcludeBlackIds() {
        List<Long> blackCantonList = Arrays.asList(5L, 6L, 7L);

        return new LambdaQueryChainWrapper<>(geoCantonMapper)
                .select(GeoCanton::getId, GeoCanton::getName)
                //.notIn(GeoCanton::getId, blackCantonList)
                .list();
    }

    /**
     * 计算统计优惠券相关的信息（包括投放量、核销量、使用率）
     * （按全平台与各地区分别统计，并拼装结果返回）
     */
    public List<FinanceAllowanceStatsPresenter> couponStat(FinanceStatDto dto) {
        List<FinanceAllowanceStatsPresenter> res = new ArrayList<>();

        List<FinanceAllowanceStatsCountVo> couponUsedStat = selectCouponUsed(dto.getStartTimeStamp(), dto.getEndTimeStamp());

        List<FinanceAllowanceCouponMongoStatsVo> cantonCouponGranted = selectCantonCouponGrantedAll(dto.getStartTimeStamp(), dto.getEndTimeStamp());

        // 平台总计
        FinanceAllowanceStatsPresenter platform = new FinanceAllowanceStatsPresenter();
        FinanceAllowanceStatsPresenter.MainBody platformBody = new FinanceAllowanceStatsPresenter.MainBody();
        FinanceAllowanceStatsPresenter.MainData platformData = new FinanceAllowanceStatsPresenter.MainData();
        platformBody.setMainBodyType(Enum.MainBodyType.platform);

        Long platformCouponGrantedCount = 0L;
        Map<Long, Long> cantonCouponGrantedMap = null;
        if (ObjectUtil.isNotEmpty(cantonCouponGranted)) {
            cantonCouponGrantedMap = cantonCouponGranted.stream().collect(Collectors.toMap(FinanceAllowanceCouponMongoStatsVo::get_id, FinanceAllowanceCouponMongoStatsVo::getCount));
            platformCouponGrantedCount = cantonCouponGranted.stream().mapToLong(FinanceAllowanceCouponMongoStatsVo::getCount).sum();
            platformData.setCouponGrantedCount(platformCouponGrantedCount);
        }

        Long platformCouponUsedCount = 0L;
        Map<Long, Long> couponUsedStatMap = null;
        if (ObjectUtil.isNotEmpty(couponUsedStat)) {
            couponUsedStatMap = couponUsedStat.stream().collect(Collectors.toMap(FinanceAllowanceStatsCountVo::getCantonId, FinanceAllowanceStatsCountVo::getNum));
            platformCouponUsedCount = couponUsedStat.stream().mapToLong(FinanceAllowanceStatsCountVo::getNum).sum();
            platformData.setCouponUsedCount(platformCouponUsedCount);
        }

        if (!platformCouponGrantedCount.equals(0L)) {
            platformData.setCouponUsedRate(NumberUtil.round(NumberUtil.div(platformCouponUsedCount, platformCouponGrantedCount), 2));
        }

        platformBody.setMainBodyType(Enum.MainBodyType.platform);

        platform.setMainData(platformData);
        platform.setMainBody(platformBody);

        // 设置平台总计
        res.add(platform);

        Map<Long, Long> finalCantonCouponGrantedMap = cantonCouponGrantedMap;
        Map<Long, Long> finalCouponUsedStatMap = couponUsedStatMap;
        dto.getCantonList().forEach(canton -> {
            FinanceAllowanceStatsPresenter cantonItemRes = new FinanceAllowanceStatsPresenter();
            FinanceAllowanceStatsPresenter.MainBody cantonResBody = new FinanceAllowanceStatsPresenter.MainBody();
            FinanceAllowanceStatsPresenter.MainData cantonResData = new FinanceAllowanceStatsPresenter.MainData();

            // mainBody
            cantonResBody.setMainBodyType(Enum.MainBodyType.canton);
            cantonResBody.setId(canton.getId());
            cantonResBody.setName(canton.getName());

            cantonItemRes.setMainBody(cantonResBody);

            // mainData
            Long itemCouponUsedCount = 0L;
            if (Objects.nonNull(finalCouponUsedStatMap)) {
                itemCouponUsedCount = finalCouponUsedStatMap.getOrDefault(canton.getId(), 0L);
                cantonResData.setCouponUsedCount(itemCouponUsedCount);
            }

            Long itemCouponGrantedCount = 0L;
            if (Objects.nonNull(finalCantonCouponGrantedMap)) {
                itemCouponGrantedCount = finalCantonCouponGrantedMap.getOrDefault(canton.getId(), 0L);
                cantonResData.setCouponGrantedCount(itemCouponGrantedCount);

                if (!itemCouponGrantedCount.equals(0L)) {
                    cantonResData.setCouponUsedRate(NumberUtil.round(NumberUtil.div(itemCouponUsedCount, itemCouponGrantedCount), 2));
                }
            }

            cantonItemRes.setMainData(cantonResData);

            res.add(cantonItemRes);
        });

        return res;
    }

    private List<FinanceAllowanceStatsCountVo> selectCouponUsed(Integer startTimeStamp, Integer endTimeStamp) {
        return couponMapper.selectCouponUsed(startTimeStamp, endTimeStamp);
    }

    private List<FinanceAllowanceCouponMongoStatsVo> selectCantonCouponGrantedAll(Integer startTimeStamp, Integer endTimeStamp) {
        if (ObjectUtil.isNotEmpty(startTimeStamp) && ObjectUtil.isNotEmpty(endTimeStamp)) {
            return couponMongoRepository.cantonCouponGrantedWithCondition(startTimeStamp, endTimeStamp);
        }

        return couponMongoRepository.cantonCouponGrantedAll();
    }

    public List<FinanceIncomeBelongStatsPresenter> incomeBelongStatistics(FinanceIncomeBelongStatDto dto) {
        List<Long> companyIds = dto.getCompanyList().stream().map(Company::getId).collect(toList());

        return incomeBelongingLogMapper.selectFinanceStat(companyIds, dto.getStartDate(), dto.getEndDate());
    }

    public FinanceCustomerAllowancePresenter customerAllowance(FinanceReconciliationDto dto) {
        FinanceCustomerAllowancePresenter res = new FinanceCustomerAllowancePresenter();

        BigDecimal wxminaRedPacket = wechatRedpacketWithdrawMapper.selectCustomerAllowance(dto.getStartTime(), dto.getEndTime());
        BigDecimal balanceAllowance = rideTransactionLogMapper.selectCustomerAllowanceBalance(dto.getStartTime(), dto.getEndTime());
        BigDecimal couponAllowance = rideTransactionLogMapper.selectCustomerAllowanceCoupon(dto.getStartTime(), dto.getEndTime());
        BigDecimal waveAllowance = activityWaveAllowanceLogMapper.selectCustomerAllowance(dto.getStartTime(), dto.getEndTime());
        BigDecimal scanRideAllowance = scanRideOrderMapper.selectCustomerAllowance(dto.getStartTime(), dto.getEndTime());

        res.setWxminaRedPacket(NumberUtil.round(wxminaRedPacket, 2));
        res.setBalanceAllowance(NumberUtil.round(balanceAllowance, 2));
        res.setCouponAllowance(NumberUtil.round(couponAllowance, 2));
        res.setWaveAllowance(NumberUtil.round(waveAllowance, 2));
        res.setScanRideAllowance(NumberUtil.round(scanRideAllowance, 2));

        return res;
    }

    public BigDecimal serviceFee(FinanceReconciliationDto dto) {
        BigDecimal value = incomeBelongingLogMapper.selectServiceFee(dto.getStartTime(), dto.getEndTime());

        return NumberUtil.round(value, 2);
    }

    public FinanceDriverRewardPresenter driverReward(FinanceReconciliationDto dto) {
        FinanceDriverRewardPresenter res = new FinanceDriverRewardPresenter();

        List<FinanceDriverRewardAmountVo> list = driverRewardMapper.selectFinanceDriverRideReward(dto.getStartTime(), dto.getEndTime());
        if (ObjectUtil.isNotEmpty(list)) {
            list.forEach(i -> {
                BigDecimal itemAggregate = NumberUtil.round(i.getAggregate(), 2);

                switch (i.getCategory()) {
                    case onlineOrder:
                        res.setRideReward(itemAggregate);
                        break;
                    case redPacket:
                        if (i.getIsVerified()) {
                            res.setRideRedPacketOpened(itemAggregate);
                        } else {
                            res.setRideRedPacketUnopened(itemAggregate);
                        }
                        break;
                    case recommend:
                        res.setRecommendReward(itemAggregate);
                        break;
                    case recommendation_first_order_reward:
                        res.setRecommendFirstOrderReward(itemAggregate);
                        break;
                }
            });
        }

        return res;
    }

    public FinanceDriverFundsInOutPresenter driverFundsInOut(FinanceReconciliationDto dto) {
        FinanceDriverFundsInOutPresenter res = new FinanceDriverFundsInOutPresenter();

        BigDecimal in = rideOrderMapper.selectDriverFundsIn(dto.getStartTime(), dto.getEndTime());
        BigDecimal outWithdraw = driverWithdrawMapper.selectDriverFundsOut(dto.getStartTime(), dto.getEndTime());
        BigDecimal outDeducting = driverMoneyDeductingMapper.selectDriverFundsOut(dto.getStartTime(), dto.getEndTime());

        res.setIn(NumberUtil.round(in, 2));
        res.setOutWithdraw(NumberUtil.round(outWithdraw, 2));
        res.setOutDeducting(NumberUtil.round(outDeducting, 2));

        return res;
    }

    public BigDecimal driverUndrawnBalance(DateTime endTime) {
        BigDecimal in = rideOrderMapper.selectDriverUndrawnBalanceAmountIn(endTime);
        BigDecimal out = driverRewardMapper.selectDriverUndrawnBalanceAmountOut(endTime);

        BigDecimal res = NumberUtil.sub(NumberUtil.round(in, 2), NumberUtil.round(out, 2));

        return NumberUtil.round(res, 2);
    }

    public FinancePaymentPlatformFeePresenter paymentPlatformFee(FinanceReconciliationDto dto) {
        FinancePaymentPlatformFeePresenter res = new FinancePaymentPlatformFeePresenter();

        BigDecimal ali = rideOrderMapper.selectFinancePaymentPlatformFeeAli(dto.getStartTime(), dto.getEndTime());
        BigDecimal wechat = rideOrderMapper.selectFinancePaymentPlatformFeeWechat(dto.getStartTime(), dto.getEndTime());
        BigDecimal wxmina = rideOrderMapper.selectFinancePaymentPlatformFeeWxmina(dto.getStartTime(), dto.getEndTime());
        BigDecimal unionPay = rideOrderMapper.selectFinancePaymentPlatformFeeUnionPay(dto.getStartTime(), dto.getEndTime());

        res.setAlipay(NumberUtil.round(ali, 2));
        res.setWechat(NumberUtil.round(wechat, 2));
        res.setWxmina(NumberUtil.round(wxmina, 2));
        res.setUnionPay(NumberUtil.round(unionPay, 2));

        return res;
    }

}
